Skip to content

feat: add GPIO pin interface with embedded-hal-async digital trait support#21

Merged
jerrysxie merged 12 commits intoOpenDevicePartnership:mainfrom
jerrysxie:add-gpio-interface
Feb 27, 2026
Merged

feat: add GPIO pin interface with embedded-hal-async digital trait support#21
jerrysxie merged 12 commits intoOpenDevicePartnership:mainfrom
jerrysxie:add-gpio-interface

Conversation

@jerrysxie
Copy link
Copy Markdown
Contributor

@jerrysxie jerrysxie commented Nov 25, 2025

Summary

Adds a GPIO pin interface to the PCAL6416A driver, enabling individual pin control through the embedded-hal-async digital traits (InputPin, OutputPin, StatefulOutputPin). This allows users to pass individual GPIO pins to HAL-generic drivers and async tasks.

Built on top of #20.

Changes

New types and API:

  • Port enum (Port0, Port1) and Pin enum (Pin0Pin7) to model the device's 2×8 GPIO layout
  • IoPin struct representing a single GPIO pin with async read/write/toggle operations
  • SharedDevice wrapper using embassy_sync::Mutex for safe concurrent access across pins
  • SharedDevice::split() method returning an array of 16 IoPin instances

embedded-hal trait implementations:

  • embedded_hal::digital::ErrorType for IoPin
  • embedded_hal_async::digital::InputPin for IoPin
  • embedded_hal_async::digital::OutputPin for IoPin
  • embedded_hal_async::digital::StatefulOutputPin for IoPin
  • embedded_hal::digital::Error for Pcal6416aError

Device-level pin methods (sync + async):

  • is_pin_high / is_pin_low
  • set_pin_high / set_pin_low
  • is_set_pin_high / is_set_pin_low
  • toggle_pin

Concurrency model:

  • Replaced UnsafeCell-based interior mutability with embassy_sync::Mutex for safe shared access
  • Each IoPin holds a reference to the shared mutex, so multiple pins can be passed to independent async tasks

New dependencies:

  • embassy-sync 0.7.2
  • embedded-hal / embedded-hal-async patched from git for latest async trait definitions

Tests:

  • Comprehensive test coverage for all pin operations (both Port 0 and Port 1)
  • Tests for embedded-hal-async trait compliance
  • All async tests run under tokio

Usage example

let device = SharedDevice::new(Device::new(Pcal6416aDevice { addr_pin: AddrPinState::Low, i2cbus }));
let pins = device.split();

// Pass individual pins to different async tasks
let led_pin = pins[0];   // Port0, Pin0
let btn_pin = pins[8];   // Port1, Pin0

led_pin.set_high_async().await?;
let pressed = btn_pin.is_high_async().await?;

@jerrysxie
Copy link
Copy Markdown
Contributor Author

@EmilNorden Thoughts?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a GPIO interface for the PCAL6416A I/O expander driver, providing high-level pin manipulation APIs. The PR builds on top of #20 and increments the version to 0.3.0.

Key changes:

  • Introduces a Pin enum representing the 16 GPIO pins with helper methods for port/bit mapping
  • Adds pin manipulation methods: is_pin_high, is_pin_low, set_pin_high, set_pin_low, toggle_pin, is_pin_set_high, is_pin_set_low
  • Extends device register definitions with input ports and pull-up/down control registers

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 18 comments.

File Description
src/lib.rs Adds Pin enum and GPIO manipulation methods to the Device implementation; includes comprehensive test coverage
device.yaml Defines INPUT_PORT0/1, PULL_UP_DOWN_ENABLE_PORT0/1, and PULL_UP_DOWN_SELECT_PORT0/1 registers with field-level documentation
Cargo.toml Bumps version from 0.2.0 to 0.3.0

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jeffglaum jeffglaum moved this to In review in ODP Backlog Nov 25, 2025
Copilot AI review requested due to automatic review settings November 25, 2025 23:16
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copilot AI review requested due to automatic review settings November 26, 2025 00:45
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@EmilNorden
Copy link
Copy Markdown
Contributor

EmilNorden commented Nov 27, 2025

@EmilNorden Thoughts?

I think it's a great interface, creating a pin abstraction on top of the registers makes it so much easier to use correctly.
My only concern would be if there is a plan to go through with the proposal in issue #2 (I hope so!). That would render this interface obsolete. It's not fun to change up the public interface too much between releases, so it's just something to consider.

@jerrysxie
Copy link
Copy Markdown
Contributor Author

@EmilNorden Thoughts?

I think it's a great interface, creating a pin abstraction on top of the registers makes it so much easier to use correctly. My only concern would be if there is a plan to go through with the proposal in issue #2 (I hope so!). That would render this interface obsolete. It's not fun to change up the public interface too much between releases, so it's just something to consider.

@EmilNorden Excellent point, the plan is to move toward embedded-hal impl, but I don't want to have a bunch of pins all have mutex protecting the driver instance they all shared. Let me brew on this a bit more.

Add comprehensive GPIO pin interface with individual pin control:
- Implement IoPin type for individual pin operations
- Add Device::split() method to create array of 16 IoPin instances
- Support both sync and async pin operations (set/read/toggle)
- Implement embedded-hal digital traits (InputPin, OutputPin,
  StatefulOutputPin)
- Add full async GPIO methods to Device (set_pin_high_async, etc.)
- Implement digital::Error trait for Pcal6416aError
- Add comprehensive test coverage for pin operations and
  embedded-hal trait compliance
- Temporarily disable unsafe_code lint to allow UnsafeCell for
  interior mutability

The split() API enables passing individual pins to different
functions while maintaining shared access to the underlying I2C
device through safe interior mutability patterns.
Improved test coverage for pin operations by testing pins from
both ports (port 0 and port 1) in each test case. This ensures
the logic is correctly validated across different register
addresses since the device has separate registers for each port.

Changes:
- Updated sync pin tests (is_high, is_low, set_high, set_low,
  toggle, is_set_high) to test both Pin0 (port 0) and Pin15
  (port 1)
- Updated async pin tests (is_high_async, is_low_async,
  set_high_async, set_low_async, toggle_async,
  is_set_high_async, is_set_low_async) to test both Pin0 and
  Pin15
- Added inline comments to test expectations for clarity
Replace the UnsafeCell-based interior mutability pattern
with embassy-sync's Mutex for safe concurrent access to
individual GPIO pins.

Changes:
- Add SharedDevice wrapper with Mutex<M, Device>
- Update IoPin to use &Mutex instead of &UnsafeCell
- Remove synchronous pin methods, keep only async variants
- Implement embedded-hal-async traits instead of blocking
  traits
- Update all tests to use SharedDevice and async patterns
- Add embassy-sync dependency (0.7.2)
- Re-enable unsafe_code lint (was commented out)
- Add embedded-hal git patches for latest async traits

This provides proper mutex-based synchronization for
safe concurrent pin access in async contexts.
Copilot AI review requested due to automatic review settings February 12, 2026 02:08
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 6 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@jerrysxie
Copy link
Copy Markdown
Contributor Author

@EmilNorden @felipebalbi Please take a look at the async implementation of embedded-hal::digital.

Split the GPIO pin model into separate Port and Pin enums so
that each pin is identified by a (Port, Pin) pair instead of a
single Pin0-Pin15 value.

- Add Port enum with Port0 and Port1 variants
- Reduce Pin enum from 16 variants (Pin0-Pin15) to 8 (Pin0-Pin7)
- Add port field to IoPin struct alongside existing pin field
- Update all Device methods (sync and async) to accept
  (port: Port, pin: Pin) parameters
- Replace if/else port checks with exhaustive match on Port enum
- Update SharedDevice::split() to assign correct Port to each pin
- Update all tests to use new (Port, Pin) API
@jerrysxie jerrysxie added the enhancement New feature or request label Feb 22, 2026
@jerrysxie jerrysxie changed the title RFC: add GPIO interface Add embedded-hal digital trait support Feb 22, 2026
@jerrysxie jerrysxie changed the title Add embedded-hal digital trait support feat: add GPIO pin interface with embedded-hal-async digital trait support Feb 22, 2026
@jerrysxie jerrysxie marked this pull request as ready for review February 22, 2026 18:38
@jerrysxie jerrysxie requested a review from a team as a code owner February 22, 2026 18:38
Copilot AI review requested due to automatic review settings February 22, 2026 18:38
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 10 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

`// SAFETY:` is reserved for `unsafe` blocks.
These methods use safe async mutex locks.
- Add doc comments to SharedDevice struct and ::new()
- Make SharedDevice.device field private
@jerrysxie jerrysxie merged commit 07cccfc into OpenDevicePartnership:main Feb 27, 2026
8 of 9 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in ODP Backlog Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

Extend PCAL6416A driver to implement digital::InputPin and digital::OutputPin from embedded-hal

7 participants